package com.ty.user.starter.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ty.user.starter.constants.Constant;
import com.ty.user.starter.entity.User;
import com.ty.user.starter.entity.criteria.UserCriteria;
import com.ty.user.starter.entity.dto.ChangPwdDto;
import com.ty.user.starter.entity.dto.UserDto;
import com.ty.user.starter.entity.dto.UserLoginDto;
import com.ty.user.starter.entity.vo.RoleVo;
import com.ty.user.starter.entity.vo.UserVo;
import com.ty.user.starter.exception.UserMgtException;
import com.ty.user.starter.mapper.DeptMapper;
import com.ty.user.starter.mapper.UserMapper;
import com.ty.user.starter.mapper.UserRoleMapper;
import com.ty.user.starter.properties.UserMgtProperties;
import com.ty.user.starter.service.UserService;
import com.ty.user.starter.util.MD5Utils;
import com.ty.user.starter.util.PBKDF2Util;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.UUID;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author minku
 * @since 2021-10-15
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Resource
    private UserRoleMapper userRoleMapper;
    @Resource
    private DeptMapper deptMapper;

    private final UserMgtProperties userMgtProperties;

    @Autowired
    public UserServiceImpl(UserMgtProperties userMgtProperties) {
        this.userMgtProperties = userMgtProperties;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(UserDto dto) throws UserMgtException {
        Assert.isNull(dto.getId(), Constant.ID_IS_NULL);
        this.validDept(dto.getDeptId());
        this.validUsername(dto.getUsername(), null);

        User user = new User();
        BeanUtils.copyProperties(dto, user);
        this.setPhoto(user, dto.getFile());
        // 密码加密
        String salt = PBKDF2Util.generateSalt();
        user.setSalt(salt);
        user.setPwd(MD5Utils.encodePassword(dto.getPassword(), salt));
        baseMapper.insert(user);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void edit(UserDto dto) throws UserMgtException {
        Assert.notNull(dto.getId(), Constant.ID_NOT_NULL);
        this.validDept(dto.getDeptId());
        User user = this.get(dto.getId());
        this.validUsername(dto.getUsername(), dto.getId());
        BeanUtils.copyProperties(dto, user);
        // 如果密码有修改则更新加密盐和密码
        if (!MD5Utils.encodePassword(dto.getPassword(), user.getSalt()).equals(user.getPwd())) {
            String salt = PBKDF2Util.generateSalt();
            user.setSalt(salt);
            user.setPwd(MD5Utils.encodePassword(dto.getPassword(), salt));
        }
        this.setPhoto(user, dto.getFile());
        baseMapper.updateById(user);
    }

    /**
     * 验证部门信息
     *
     * @param deptId 部门id
     */
    public void validDept(Long deptId) {
        if (null != deptId) {
            Assert.notNull(deptMapper.selectById(deptId), "部门信息不存在");
        }
    }

    /**
     * 头像设置
     *
     * @param user 用户信息
     * @param file 上传文件
     */
    public void setPhoto(@NotNull User user, MultipartFile file) throws UserMgtException {
        if (null != file && !file.isEmpty()) {
            // 上传类型校验
            String name = file.getOriginalFilename();
            Assert.notNull(name, "文件名称不能为空");
            int index = name.lastIndexOf(".");
            Assert.isTrue(index > 0, "所上传文件必须包含格式信息");
            // 后缀名
            String suffix = name.substring(index + 1);
            Assert.isTrue(CollectionUtils.arrayToList(userMgtProperties.getPhotoType()).contains(suffix), String.format("上传类型:%s系统不支持", suffix));
            File dir = new File(userMgtProperties.getPhotoPath());
            if (!dir.exists()) {
                Assert.isTrue(dir.mkdirs(), String.format("文件目录:%s 创建失败", userMgtProperties.getPhotoPath()));
            }

            // 重命名以及上传文件
            StringBuilder fileName = new StringBuilder(UUID.randomUUID().toString()).append(".").append(suffix);
            File writeFile = new File(userMgtProperties.getPhotoPath(), fileName.toString());
            try {
                file.transferTo(writeFile);
            } catch (IOException e) {
                log.error("图片写入错误", e);
                throw new UserMgtException("图片写入错误");
            }
            user.setHeadUrl(fileName.toString());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void changPwd(@NonNull ChangPwdDto dto) throws UserMgtException {
        // 验证新旧密码是否相同
        Assert.isTrue(!dto.getNewPwd().equals(dto.getCurrentPwd()), "新旧密码不能相同");
        User user = this.get(dto.getUserId());
        // 验证旧密码是否与系统中的一致
        String encryptedPassword = MD5Utils.encodePassword(dto.getCurrentPwd(), user.getSalt());
        Assert.isTrue(encryptedPassword.equals(user.getPwd()), "密码错误");
        // 修改密码并重新生成加密盐
        String salt = PBKDF2Util.generateSalt();
        user.setSalt(salt);
        user.setPwd(MD5Utils.encodePassword(dto.getNewPwd(), salt));
        baseMapper.updateById(user);
    }

    @Override
    public User getByUsername(String username) {
        QueryWrapper<User> query = new QueryWrapper<>();
        query.eq("username", username);
        List<User> users = baseMapper.selectList(query);
        Assert.notEmpty(users, "用户名不存在");
        return users.get(0);
    }

    @Override
    public UserVo getById(long userId) {
        User user = this.get(userId);
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user, userVo);
        return userVo;
    }

    @Override
    public List<RoleVo> getRoles(long userId) {
        return userRoleMapper.findByUserId(userId);
    }

    @Override
    public UserVo login(UserLoginDto dto) {
        UsernamePasswordToken token = new UsernamePasswordToken(dto.getUsername(), dto.getPassword());
        Subject subject = SecurityUtils.getSubject();
        subject.login(token);
        User user = (User) subject.getPrincipal();
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user, userVo);
        return userVo;
    }

    @Override
    public IPage<UserVo> findAll(UserCriteria criteria) {
        return baseMapper.findAll(criteria.defaultPage(), criteria);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchSetDept(long deptId, List<Long> userIds) {
        Assert.notNull(deptMapper.selectById(deptId), "部门信息不存在");
        Assert.notEmpty(userIds, "用户信息不能为空");
        userIds.parallelStream().forEach(userId -> {
            User user = this.get(userId);
            user.setDeptId(deptId);
            baseMapper.updateById(user);
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchSetState(boolean state, List<Long> userIds) {
        Assert.notEmpty(userIds, "用户信息不能为空");
        userIds.parallelStream().forEach(userId -> {
            User user = this.get(userId);
            user.setState(state);
            baseMapper.updateById(user);
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(long userId) throws UserMgtException {
        User user = this.get(userId);
        // 删除头像
        if (StringUtils.hasText(user.getHeadUrl())) {
            File file = new File(userMgtProperties.getPhotoPath() + user.getHeadUrl());
            if (file.isFile()) {
                try {
                    Files.delete(file.toPath());
                } catch (IOException e) {
                    log.error("头像删除失败", e);
                    throw new UserMgtException("头像删除失败");
                }
            }
        }
        baseMapper.deleteById(userId);
    }

    /**
     * 验证用户名是否重复
     *
     * @param username 用户名
     * @param id       用户id
     */
    public void validUsername(String username, Long id) {
        QueryWrapper<User> query = new QueryWrapper<>();
        query.eq("username", username);
        if (null != id) {
            query.ne("id", id);
        }
        Assert.isTrue(baseMapper.selectCount(query) == 0, "用户名已存在");
    }

    /**
     * 查询用户
     *
     * @param id 用户id
     * @return 用户信息
     */
    public User get(long id) {
        User user = baseMapper.selectById(id);
        Assert.notNull(user, "用户信息不存在");
        return user;
    }
}
